﻿(function(){
	//empty function for convenience
	var $ef = function(){};
	
	var $$ = function(){
		if (! (this instanceof $$)) 
			return $$.createInstance ($$, arguments);
	};
	
	$$.prototype.destroy = function(){
		this.unbind();
		
		return null;
	}
	
	//the generated handler will be binded to the dom element directly
	$$.prototype._genDefaultHandler = function (eventName) {
		var handlerName = 'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
		return $$.bind(function(e){
			// check if there is any native event handler defined.
			return this[handlerName] ? this[handlerName](e) : this.trigger(eventName, e);
		},this);
	};
	
	/*
		jkui.events		***
	*/
	$$.prototype.bind = $$.bind = function(name, handler, checkDup){
		//bind is for multiple purpose
		
		//bind the "this" pointer of a function with an object permenantly
		if (arguments.length == 2 && typeof arguments[0] == 'function' && typeof arguments[1] == 'object') {
			var func = arguments[0]; var THIS = arguments[1];
			return function () { func.apply (THIS, arguments); }
		}
		
		if (! this instanceof jkui) return this;

		//bind multiple event handlers to each jkui object
		if (typeof arguments[0] == 'object') {
			var args = arguments;
			if (arguments.length == 2) {
				$$.each(arguments[1], $$.bind(function(i, eventName){
					this.bind(eventName, args[0][eventName]);
				}, this));
			} else {
				for (var eventName in arguments[0])
					this.bind (eventName, arguments[0][eventName]);
			}
			return this;
		}
		
		if(arguments.length < 2 || typeof name != 'string' || typeof handler != 'function')
			return this;
		
		//bind an event handler to each jkui object
			
		//default value for checkDup is true
		checkDup = arguments.length == 2 ? true : checkDup;
		
		//initialize event list if first bond the event handler
		typeof this.Events != 'object' && (this.Events = {});
		!this.Events[name] && (this.Events[name] = {counter:{}, list:[]});
		
		//if there isn't a duplicated handler, or user specified not to check duplication, register the handler
		if (!this.Events[name].counter[handler] || !checkDup && this.Events[name].counter[handler]) {
			this.Events[name].list.push(handler);
			typeof this.Events[name].counter[handler] == 'number'? ( this.Events[name].counter[handler] ++ ) : this.Events[name].counter[handler] = 1;
		}
		
		return this;
	};
	$$.prototype.unbind = function(name, handler, checkDup){
		//unbind an event handler to each ui object 
		
		//illegal calls or no events registered
		if (!this instanceof $$ || typeof this.Events != 'object') return this;
		
		//unbind all events
		if (arguments.length == 0) {
			for (var eventName in this.Events)
				this.unbind (eventName);
			return this;
		}
		
		//no handler passed, remove all event handlers of this event
		if (arguments.length == 1) { delete this.Events[name]; return this; }
		
		checkDup = arguments.length == 2 ? true : checkDup;
		for (var i_name = 0; i_name < this.Events[name].list.length; i_name++) {
			//if number of handlers reaches 0, stop looking for matched handlers
			
			if (this.Events[name].list[i_name] == handler) {
				this.Events[name].list.splice(i_name, 1);
				this.Events[name].counter[handler] --;
				i_name--;
				
				//if user doesn't want to remove duplicated handlers, also stop checking after found the first match
				if (!checkDup) break;
			}
		}
		
		return this;
	};
	$$.prototype.trigger = function (name){
		//trigger the registered event handlers with parameters and a customized "this" pointer
		
		if (!this.Events || !this.Events[name] || ! typeof name == 'string') return true;
		var THIS = this;
		return jkui.all (this.Events[name].list, 
			function() {return this.apply (THIS, Array.prototype.slice.call(arguments, 1));}, 
			function(x) {return x !== false;}, this);
	};
	
	
	/*
		Utilities		***
	*/
	
	$$.delay = function(func, interval, THIS) {
		return setTimeout ($$.bind(func, THIS), interval);
	};
	
	$$.returnFalse = function(){return false;}
	$$.returnTrue = function(){return true;}
	
	/*
		Accessors		***
		TODO: map
	*/
	$$.each = function(array, closure, trueFn){
		//the "each" accessor
		
		if (typeof array == 'undefined')
			return [];
		
		typeof closure == 'undefined' && (closure = function(i, x) {return x});
		if(typeof closure != 'function') {
			var c = closure;
			closure = function(i, x){return x == c}
		}
		var ret = [];
		var args = Array.apply(null, arguments).slice(2);
		
		//setup default "isTrue" function to kick out undefined, boolean false or null values, but preserve numbers
		var isTrue = typeof trueFn == 'function' ? trueFn : function(x) {return x || x === 0;};
		
		var iterate = function (i, array) {
			var rval = closure.apply(array[i], [i].concat(args).concat([array[i]]))
			isTrue(rval) && (ret.push(rval));
		}
		
		if (typeof (array.length) == 'number') {
			for(var i = 0; i < array.length; i++) 
				iterate(i, array);
		} else {
			for(var a in array) 
				iterate(a, array);
		}
		return ret;
	};
	
	$$.trim = function(array, trueFn){
		//remove any false values of an array at the end
		return $$.each(array);
	};
	
	$$.all = function(array, closure, trueFn){
		//test for each element in the array, whether all of them make the closure to return true
		return this.each(array, closure, trueFn).length == array.length;
	};
	
	$$.any = function(array, closure, trueFn){
		//test for each element in the array, whether any of them make the closure to return true
		return this.each(array, closure).length > 0;
	};
	
	/*
		jkui object oriented interface		***
	*/
	$$.extend = function (parent, child, implementation) {
		//extend a parent class
		
		if (typeof parent != 'function' || typeof child != 'function')
			throw new Error ('Parent and child are not both constructors');
		
		//to make sure before calling child's constructor after "Super" is called there is only the parent's destructor
		//we need to remove the "destroy" property from prototype
		var originalDesP = parent.prototype.destroy;

		var originalDesC = $ef,
			desC = $ef, 
			desP = $ef;
		
		var args = arguments;
		
		//determine starting from which are the implementation injection arguments
		var implArgsStart = 2;
		//implementations to be injected
		var implementations = [];
		
		args.length > 3 && args[2] == 'abstract' && (implArgsStart = 3);
		
		var ret = function () {
			if (!(this instanceof $$))
				return $$.createInstance (ret, arguments);
			
			//provided a short hand to call this.super.constructor.apply(this, arguments);
			//must be called before setting up any members
			this.Super = function(){
				
				var _retProtoDes = ret.prototype.destroy;
				desC = originalDesC = this.destroy || _retProtoDes || $ef;
				
				//make sure the parent will get the right destructor of itself
				delete this.destroy;
				delete ret.prototype.destroy;
				
				try {
					parent.apply(this, arguments);
				} catch (e) {
					//if the abstract class is the parent, an error will be thrown
					if (e.message == 'AbstractClassInstatiationException') {
						//turn on the flag and call again
						this.callingFromDerivedClass = true;
						parent.apply(this, arguments);
						delete this.callingFromDerivedClass;
					}
					else 
						throw e;
				}
				
				//restore the destructor when parent constructor is applied
				ret.prototype.destroy = _retProtoDes;
				
				desP = this.destroy && this.destroy.pure	//if it's on the lower levels, which we have reset the destroy property
															//only destroy.pure is needed to prevent multiple triggering of destroyed/destroying events
						
					|| this.destroy || originalDesP || $ef;	//otherwise it's in the initial level where the destructor packing starts
															//we need to retrieve the initial destructor defined by user
				
				//make sure the base class's destructor to be called, because base class will not pack destructors when it's constructor is called
				desP = desC == desP ? originalDesP : desP;
			};
			
			//abstract class prevents from instantiation, but this will also prevent calling the constructor from a derived class instantiation
			//so, 	1.	to allow calling from derived class, we catch the specific error in the "Super" function, and turn on the callingFromDerivedClass flag
			//		2.	this.Super need still be set because otherwise will cause an infinite recursion error since "this.Super" does not change from the 
			//			previous level of calling
			try {
				if (args[2] == 'abstract' && !this.callingFromDerivedClass) 
					throw new Error ('AbstractClassInstatiationException');
			} catch (e) {
				throw e;
			}
			
			child.apply(this, arguments);
			
			//inject implementations into the function prototype
			for (var i = implArgsStart; i < args.length; i++) 
				typeof args[i] == 'function' && (args[i].apply(this, arguments));
			
			//if user sets destructor in the constructor using "this.destroy = function(){...}"
			//we need to retrieve that from this.destroy
			desC = this.destroy && this.destroy.pure || this.destroy || desC;
			
			desC = desC == desP ? originalDesC : desC;
			
			//the interface for calling, which triggers "destroying" and "destroyed" events only once along the destructor chain
			this.destroy = function(){
				if (!this.trigger ('destroying'))
					return this;
				this.destroy.pure();
				this.trigger ('destroyed');
				return null;
			}
			
			//one "ring" in the destructor chain
			this.destroy.pure = (function(dp, dc){
				return function () {
					//call destructor of child first, then parent
					dc(); dp();
				}
			})($$.bind(desP,this), $$.bind(desC,this));
		};
		
		ret.prototype = $$.createInstance(parent, [], false);
		ret.prototype.SuperClass = parent;
		
		//inject implementations into the function prototype
		for (var i = implArgsStart; i < args.length; i++) {
			var j = i - implArgsStart;
			implementations[j] = typeof args[i] == 'function' ? new args[i] : args[i];
			implementations[j] = typeof implementations[j] == 'object' ? implementations[j] : {};
		}
		
		$$.inject.apply(ret.prototype, Array.prototype.slice.call(implementations, 0));
	
		//fix the constructor property
		ret.prototype.constructor = ret;
		ret.prototype.destroy = $ef;

		return ret;
	};
	
	$$.abstractExtend = function (parent, child) {
		return $$.extend.call(null, parent, child, 'abstract');
	};
	
	$$.inject = $$.prototype.inject = function () {
		//inject an implementation into the object
		
		for (var i = 0; i < arguments.length; i++) {
			if (typeof arguments[i] == 'function') {
				arguments[i].apply (this);
				arguments[i] = new arguments[i];
			}
			
			$$.each(arguments[i], function(attr, me) {
				me[attr] = this;
			}, this);
		}
	};
	
	$$.createInstance = function (constructor, args, callParentConstructor) {
		//enables "apply arguments" to object instantiation
		
		//default value is true
		callParentConstructor = arguments.length <= 2 ? true: callParentConstructor;
		
		var tmpCons = $ef;
		tmpCons.prototype = constructor.prototype;
		var tmpIns = new tmpCons();
		
		callParentConstructor && (constructor.apply(tmpIns, args));
		
		return tmpIns;
	};
	
	/*
		Debuggers
	*/
	$$.assert = function(value, message) {
		try{
			if (!value)
				throw new Error('Assert falure: ' + message);
		} catch(e){
			throw e;
		}
	};
	
	typeof jkui == 'undefined' && (jkui = $$);
})();


